/***
 * * PROJECT
 * *    ATaG
 * * VERSION
 * *    $Revision: 1.2 $
 * * DATE
 * *    $Date: 2008/10/07 12:57:36 $
 * * LOG
 * *    $Log: StickyTaskAssigner.java,v $
 * *    Revision 1.2  2008/10/07 12:57:36  animesh
 * *    removed GPL License [will re-add to all files later]
 * *
 * *    Revision 1.1  2007/06/01 20:53:19  animesh
 * *    bulk update.
 * *
 * *
 * *
 * *   ATaG (Abstract Task Graph) - Data-driven Macroprogramming for
 * *                                Sensor Networks
 * *
 ***/

package atag.compiler;

import atag.common.*;
import atag.runtime.NodeInfo;
import java.util.*;

/**
 * Assign fixed tasks where they can be assigned
 * For other tasks, if there is a source in the same set of nodes,
 * stick them together. if nothing, go random
 *
 * @author Animesh
 */
public class StickyTaskAssigner implements TaskAssigner{
    
    private Random randomizer;
    
    /** Creates a new instance of StickyTaskAssigner */
    public StickyTaskAssigner() {
    }
    /**
     * The function that assigns tasks.
     * @param aP The ATaG program
     * @param itP The ITaG version of aP
     * @param nTopo The network, with all nodes
     */
    public void assignTasks(ATaGProgram aP, ITaGProgram itP, ComposedATaG caProgram,
            NetworkTopology nTopo, int randomSeed) {
        
        //TODO initialize random number generator with randomSeed
        this.randomizer = new Random(randomSeed);
        
        // Temporary data structure to store partial task assignments
        HashMap<Integer, Set<Integer>> partialAssignment = new HashMap<Integer, Set<Integer>>();
        Iterator<NodeInfo> itNodes = nTopo.getNodes().iterator();
        while (itNodes.hasNext()) {
            partialAssignment.put(itNodes.next().getMyId(),
                    new HashSet<Integer>());
        }
        
        // first, assign the tasks which are "easy to assign"
        for (ATaGTaskDeclaration at : aP.getTaskList()) {
            int instType = at.getLocation()[0];
            int instParam = at.getLocation()[1];
            int atID = at.getID();
            
            if (instType == ATaGTaskDeclaration.INSTANTIATION_ONE_ON_NODE_ID) {
                // instantiate on that node
                // nTopo.getNodeInfoByID(instParam).assignTask(atID);
                partialAssignment.get(instParam).add(atID);
                at.assign(instParam);
            } else if ((instType == ATaGTaskDeclaration.INSTANTIATION_NODES_PER_INSTANCE)
            && (instParam == 1)) {
                // instantiate on one of each
                if (at.getNeededAttribute() != null) {
                    // find all nodes with that attribute, and assign this task
                    // on them
                    for (NodeInfo ni : nTopo.getNodesWithAttribute(at
                            .getNeededAttribute())) {
                        // ni.assignTask(at.getID());
                        partialAssignment.get(ni.getMyId()).add(at.getID());
                        at.assign(ni.getMyId());
                    }
                } else {
                    // no needed Attribute. Assign on all.
                    for (NodeInfo ni : nTopo.getNodes()) {
                        //ni.assignTask(at.getID());
                        partialAssignment.get(ni.getMyId()).add(at.getID());
                        at.assign(ni.getMyId());
                    }
                }
            } else {
                // nothing. Do nothing for this task in first pass
            }
        }
        
        // second pass. Assign tasks that have other instantiation rules
        for (ATaGTaskDeclaration at : aP.getTaskList()) {
            int instType = at.getLocation()[0];
            int instParam = at.getLocation()[1];
            int atID = at.getID();
            
            //if (at.getAssignedTo().size() == 0) {
                //if (task is not yet assigned)
                //TODO - write the code
                if(at.getFiringRule()[0] == ATaGTaskDeclaration.FIRING_PERIODIC){
                    assignTasksInStickyBFS(at, caProgram, partialAssignment, nTopo);
                }
            //}
        }
        
        // Assigning tasks to nodes
        itNodes = nTopo.getNodes().iterator();
        while (itNodes.hasNext()) {
            NodeInfo nodeInfo = itNodes.next();
            nodeInfo.assignTasks(partialAssignment.get(nodeInfo.getMyId()));
        }
        
        return;
    }
    
    private NodeInfo findNodeWithTask(ATaGTaskDeclaration at,
            ArrayList<NodeInfo> nodeList){
        //for each node - if node has the task assigned. return it.
        //Else return null
        
        for (NodeInfo testNode : nodeList){
            if (testNode.isTaskAssigned(at.getID()))
                return testNode;
        }
        
        return null;
    }
    
    private void assignTasksInStickyBFS(ATaGTaskDeclaration rootTask, ComposedATaG caProgram,
            HashMap<Integer, Set<Integer>> partialAssignment, NetworkTopology nTopo) {
        
        CATaGNode ct = caProgram.getCATaGNodeByID(rootTask.getID());
        
        //initialize queue to empty
        //LinkedList implements Queue
        Queue<CATaGNode> nodeQueue = new LinkedList();
        
        //insert "root" node
        nodeQueue.offer(ct);
        
        while (nodeQueue.peek() != null){
            //while queue is not empty
            CATaGNode thisNode = nodeQueue.poll();
            //for first item in queue
            
            //add its neighbors to the queue - here we are going breadth first
            for(CATaGEdge ngbrEdge: thisNode.getEdgeList())
                nodeQueue.offer(ngbrEdge.getDestinationNode());
            
            ATaGTaskDeclaration at = thisNode.getTask();
            if (at.getAssignedTo().size() == 0){
                //if task is yet unassigned
                
                int instType = at.getLocation()[0];
                int instParam = at.getLocation()[1];
                int atID = at.getID();
                
                switch (instType) {
                    case ATaGTaskDeclaration.INSTANTIATION_NODES_PER_INSTANCE:
                        if (instParam == 1) {
                            System.err.println("Why is task" + at.getName()
                            + "with instantiation type "
                                    + "nodes-per-instance:1 not assigned yet?");
                            System.exit(-5);
                        } else {
                            ArrayList<NodeInfo> targetNodes;
                            if (at.getNeededAttribute() == null) {
                                targetNodes = nTopo.getNodes();
                            } else {
                                // find all nodes with that attribute
                                targetNodes = nTopo.getNodesWithAttribute(at
                                        .getNeededAttribute());
                            }
                            
                            /* assign all tasks to one node, the one with a feeder*/
                            NodeInfo targetNode = null;
                            //TODO make this smarter
                            for (CATaGNode tempFeeder: thisNode.getFeederList()){
                                targetNode = findNodeWithTask(tempFeeder.getTask(), targetNodes);
                                
                                if (targetNode != null) break;
                            }
                            
                            if (targetNode != null){
                                //we found some node
                                //assign all copies here
                                for (int i = 0; i < targetNodes.size(); i = i
                                        + instParam) {
                                    NodeInfo ni = targetNode;
                                    partialAssignment.get(ni.getMyId()).add(at.getID());
                                    at.assign(ni.getMyId());
                                }
                            }else{
                                //we were not able to find any target nodes with feeder tasks,
                                //fallback to uniform assignment
                                // instantiate the tasks regularly
                                // note the stepsize in the for loop
                                for (int i = 0; i < targetNodes.size(); i = i
                                        + instParam) {
                                    NodeInfo ni = targetNodes.get(i);
                                    partialAssignment.get(ni.getMyId()).add(at.getID());
                                    at.assign(ni.getMyId());
                                }
                            }//end if targetNode
                        }//end if instparam
                        break;
                    case ATaGTaskDeclaration.INSTANTIATION_PARTITION_PER_INSTANCE:
                        // assume that regionIDs are from 0 to k-1 only
                        // that means no missing regionIDs
                        int totalRegions = nTopo.numberOfRegions(at
                                .getMyRegionName());
                        for (int regionID = 0; regionID < totalRegions; regionID++) {
                            ArrayList<NodeInfo> tempTarget = nTopo
                                    .getNodesInRegion(at.getMyRegionName(),
                                    regionID);
                            
                            //find one node with a feeder task
                            NodeInfo targetNode = null;
                            for (CATaGNode tempFeeder: thisNode.getFeederList()){
                                targetNode = findNodeWithTask(tempFeeder.getTask(), tempTarget);
                                //note that we are assuming that the scope of
                                //the feederEdge is local in region
                                //from what I know, this holds true in
                                //current ATaG programs - Animesh
                                //TODO - fix this
                                if (targetNode != null) break;
                            }
                            
                            if(targetNode != null){
                                for (int i = 0; i < instParam; i++) {
                                    // assign tasks one by one. Total instParam tasks
                                    //TODO make this smarter to scan across multiple nodes
                                    NodeInfo ni = targetNode;
                                    partialAssignment.get(ni.getMyId()).add(at.getID());
                                    at.assign(ni.getMyId());
                                }
                            }else{
                                //no targets found
                                for (int i = 0; i < instParam; i++) {
                                    // assign tasks one by one. Total instParam tasks
                                    int targetNodeIndex = (int) (this.randomizer.nextDouble() *
                                            tempTarget.size());
                                    NodeInfo ni = tempTarget.remove(targetNodeIndex);
                                    partialAssignment.get(ni.getMyId()).add(at.getID());
                                    at.assign(ni.getMyId());
                                }
                            }
                            
                        }
                        break;
                    case ATaGTaskDeclaration.INSTANTIATION_ONE_ANYWHERE:
                        
                        //find one node with a feeder task
                        NodeInfo targetNode = null;
                        for (CATaGNode tempFeeder: thisNode.getFeederList()){
                            targetNode = findNodeWithTask(tempFeeder.getTask(), nTopo.getNodes());
                            //note that we are assuming that the scope of
                            //the feederEdge is local in region
                            //from what I know, this should be ok for a 
                            //one-anywhere task- Animesh
                            //TODO - fix this
                            if (targetNode != null) break;
                        }

                        NodeInfo ni = null;
                        
                        if(targetNode != null){
                            ni = targetNode;
                        }else{
                        int destinationNode;
                            // completely random assignment -- random selects a number
                            // in [0,1)
                            destinationNode = (int) (this.randomizer.nextDouble() * nTopo
                                    .getNumberOfNodes());

                            ni = nTopo.getNodeInfoByID(destinationNode);
                        }
                        partialAssignment.get(ni.getMyId()).add(at.getID());
                        at.assign(ni.getMyId());
                        break;
                    case ATaGTaskDeclaration.INSTANTIATION_NODES_PER_INSTANCE_FRACTIONAL:
                        // do nothing.. allow this to run into default. Fix later
                    default:
                        System.err.println("Illegal instantiation type "
                                + at.getInstType() + " for task" + at.getName());
                        System.exit(-5);
                }
                
            }
            
            // get its feeder task list
            
            // for each guy in the feeder task list
            
            //see if the scope is local
        }
    }
}
